<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试节流防抖2024年03月07日</title>
  </head>
  <body>
    <button id="btn">防抖按钮</button>
    <button id="btn2">节流按钮</button>
    <button id="btn3">once函数</button>
    <button id="btn4">多触发角时函数防抖</button>
    <button id="btn5">多触发角时函数防抖</button>
    <input id="input1" placeholder="请输入" />
  </body>
  <script type="text/javascript">
    // 节流与防抖的作用都是防止函数多次调用。
    // 区别在于，假如用户一直触发这个函数，且每次触发函数的间隔小于阙值，防抖的情况下只会调用一次，而节流会每隔一定时间调用函数。
    window.onload = function () {
      // ------------------------------------------
      let btn = document.getElementById("btn");

      var save = function () {
        console.log("防抖，停止触发后n秒执行[频繁触发]");
      };
      //防抖-触发n秒后再执行，每次触发重新计时n秒再执行
      function debounce(func, wait) {
        // 4、创建一个标记用来存放定时器的返回值
        let timeout = null;
        return function () {
          // 5、每次当用户点击/输入的时候，把前一个定时器清除
          clearTimeout(timeout);
          // 6、然后创建一个新的 setTimeout，
          // 这样就能保证点击按钮后的 interval 间隔内
          // 如果用户还点击了的话，就不会执行 fn 函数
          timeout = setTimeout(func, wait);
        };
      }

      btn.onclick = debounce(save, 1000);

      // ------------------------------------------
      let btn2 = document.getElementById("btn2");

      var save2 = function () {
        console.log("节流，每隔n秒执行一次[频繁触发]");
      };
      //节流-n秒触发多次，只执行一次
      // function throttle(func, wait) {
      //   var timeout;
      //   var previous = 0;

      //   return function () {
      //     context = this;
      //     args = arguments;
      //     if (!timeout) {
      //       timeout = setTimeout(function () {
      //         timeout = null;
      //         func.apply(context, args);
      //       }, wait);
      //     }
      //   };
      // }
      // 2、节流函数体
      function throttle(fn) {
        // 4、通过闭包保存一个标记
        let canRun = true;
        return function () {
          // 5、在函数开头判断标志是否为 true，不为 true 则中断函数
          if (!canRun) {
            return;
          }
          // 6、将 canRun 设置为 false，防止执行之前再被执行
          canRun = false;
          // 7、定时器
          setTimeout(() => {
            fn.call(this, arguments);
            // 8、执行完事件（比如调用完接口）之后，重新将这个标志设置为 true
            canRun = true;
          }, 1000);
        };
      }

      btn2.onclick = throttle(save2, 1000);

      // ------------------------------------------
      // 不管怎么触发，只执行一次
      function once(func, context) {
        var result;
        return function () {
          if (func) {
            result = func.apply(context || this.arguments);
            func = null; //执行后就置空函数
          }
          return result;
        };
      }
      var save3 = function () {
        console.log("执行一次，不管怎么触发仅执行一次[频繁触发]");
      };
      let btn3 = document.getElementById("btn3");
      btn3.onclick = once(save3);
    };

    // ------------------------------------------
    //不管几个触发事件监听，每隔一段时间只执行一次
    // flag 方式失败，给定时器函数调用堆栈一个id成功！
    // var flag = "no";
    function oncePerTime(fn, wait) {
      //   if (flag == "no") {
      //     flag = "is"; //被调用过一次
      //   } else {
      //     return ()=>{};
      //   }

      //执行函数

      return function (args) {
        clearTimeout(fn.id);
        let that = this;
        let _args = args;

        fn.id = setTimeout(() => {
          fn.call(that, _args);
          flag = "no";
        }, wait);
      };
    }
    var save4 = function () {
      console.log("多个事件监听触发仅执行一次[频繁触发]");
    };
    let btn4 = document.getElementById("btn4");
    let btn5 = document.getElementById("btn5");
    let input1 = document.getElementById("input1");
    btn4.onclick = function () {
      oncePerTime(save4, 1000)();
    };
    btn5.onclick = function () {
      oncePerTime(save4, 1000)();
    };
    //js方法
    document.onkeydown = function (e) {
      var ev = document.all ? window.event : e;
      var keyCode = ev.keyCode || ev.which || ev.charCode;
      if (keyCode == 13) {
        // oncePerTime(save4, 1000);
        searchFieldInput();
      }
    };
    input1.addEventListener("keyup", function () {
      oncePerTime(save4, 1000);
    });

    function debounce1(fun, delay) {
      return function (args) {
        let that = this;
        let _args = args;
        clearTimeout(fun.id);
        fun.id = setTimeout(function () {
          fun.call(that, _args);
        }, delay);
      };
    }

    function searchFieldInput() {
      let res = debounce1(save4, 1000);
      res();
    }
    //  btn4.onclick = searchFieldInput;
    // btn5.onclick = searchFieldInput;
    // 总结：
    // 防抖：任务频繁触发的情况下，只有任务触发的间隔超过指定间隔的时候，任务才会执行。原理是维护一个计时器，规定在delay时间后触发函数，但是在delay时间内再次触发的话，就会取消之前的计时器而重新设置。这样一来，只有最后一次操作能被触发。
    // 场景示例：有个输入框，输入之后会调用接口，获取联想词。但是，因为频繁调用接口不太好，所以我们在代码中使用防抖功能，只有在用户输入完毕的一段时间后，才会调用接口，出现联想词。

    // 节流：任务频繁触发的情况下，指定时间间隔内只会执行一次任务。原理是通过判断是否到达一定时间来触发函数
    // 场景示例：懒加载要监听计算滚动条的位置，使用节流按一定时间的频率获取

    // 区别： 函数节流不管事件触发有多频繁，都会保证在规定时间内一定会执行一次真正的事件处理函数，而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下，我们需要用户在滚动页面时，每隔一段时间发一次 Ajax 请求，
    // 而不是在用户停下滚动页面操作时才去请求数据。这样的场景，就适合用节流技术来实现。
  </script>
</html>
